﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using net.entity.framework.cache;
using net.entity.framework.common;
using net.entity.framework.query;

namespace net.entity.framework {
    /// <summary>
    /// Type specific resolver for Entites
    /// </summary>
    /// <remarks>
    /// Reads the typeId from database and resolves Entity accordingly
    /// </remarks>
    public sealed class TypeSpecificResolver {
        private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;

        public static IList<KeyValuePair<EntityKey, Entity>> Resolve(DataTable table) {
            var resolvedItems = new List<KeyValuePair<EntityKey, Entity>>();
            foreach (DataRow row in table.Rows) {
                var resolved = Resolve(row);
                resolvedItems.Add(resolved);
            }
            return resolvedItems;
        }

        private static KeyValuePair<EntityKey, Entity> Resolve(DataRow row) {
            var args = new List<object>();

            var isDeleted = row["id"].ToBoolean();
            var id = row["id"].ToInt32();
            var typeId = row["typeId"].ToInt32();
            var revision = row["revision"].ToInt32();
            var creator = row["creator"].ToString();
            var timeStamp = row["timeStamp"].ToDateTime();

            args.Add(isDeleted);
            args.Add(id);
            args.Add(typeId);
            args.Add(revision);
            args.Add(creator);
            args.Add(timeStamp);

            var entityInfo = TypeIdCache.GetValue(typeId);
            var entityType = entityInfo.EntityType;
            var entityRefType = entityInfo.EntityRefType;
            var eqlEntity = EqlEntityRefCache.LookupOrCreate(entityRefType).EqlEntity;
            var propertyInfos = eqlEntity.GetType().GetPropertyInfos(Flags);
            foreach (var propertyInfo in propertyInfos) {
                if (propertyInfo.PropertyType == typeof (Terminal)) {
                    var terminal = propertyInfo.GetValue<Terminal>(eqlEntity, true);
                    args.Add(row[terminal.ColumnName]);
                } else if (propertyInfo.PropertyType.IsSubclassOf(typeof (EqlEntityRef))) {
                    var entityRef = ResolveEntityRef(propertyInfo, eqlEntity, row);
                    args.Add(entityRef);
                }
            }

            var entity = EntityInvoker.InvokeEntity(entityType, args, creator,timeStamp);
            var key = EntityKey.Create(entity);
            return new KeyValuePair<EntityKey, Entity>(key, entity);
        }

        private static EntityRef ResolveEntityRef(PropertyInfo pi, EqlEntity eqlEntity, DataRow row) {
            var eqlEntityRef = pi.GetValue<EqlEntityRef>(eqlEntity, true);
            var prefix = eqlEntityRef.AliasInfo.ColumnPrefix.IsNotNullOrEmpty()
                             ? eqlEntityRef.AliasInfo.ColumnPrefix + "_"
                             : String.Empty;
            var id = row[prefix + "id"].ToInt32();
            var typeId = row[prefix + "typeId"].ToInt32();
            //var revision = row[prefix + "revision"];
            var revision = 0;
            return EntityInvoker.InvokeEntityRef(eqlEntityRef.AliasInfo.RootObjType, id, typeId, revision);
        }
    }
}